package com.kfyang.manualgear.util;

import com.kfyang.manualgear.annotation.global.Mapper;
import com.kfyang.manualgear.annotation.global.ResultHandler;
import com.kfyang.manualgear.annotation.global.TypeHandler;
import com.kfyang.manualgear.constant.ClassConstants;
import com.kfyang.manualgear.struct.Handler;
import com.kfyang.manualgear.struct.MethodInfo;
import com.kfyang.manualgear.struct.ReturnTypeInfo;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

import javax.lang.model.element.*;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.*;
import java.util.*;

import static com.kfyang.manualgear.constant.ClassConstants.HANDLER_UTIL_CLASS;
import static com.kfyang.manualgear.constant.ClassConstants.RESULT_UTIL_CLASS;
import static com.kfyang.manualgear.constant.Constants.*;
import static com.kfyang.manualgear.util.GlobalConfigUtils.*;

public class GlobalInitializer {
    private final Set<? extends Element> configElements;

    public GlobalInitializer(Set<? extends Element> configElements) {
        this.configElements = configElements;
    }

    /**
     * 初始化
     */
    public void initEnv() {
        GlobalConfigUtils.objectElement = elementUtils.getTypeElement(ClassConstants.OBJECT_CLASS);

        addDefaultHandler();
        readConfigFile();
        readConfigClass();
    }

    /**
     * 添加默认的类型处理器和返回值处理器
     */
    private void addDefaultHandler() {
        addTypeHandler(elementUtils.getTypeElement(HANDLER_UTIL_CLASS));
        addResultHandler(elementUtils.getTypeElement(RESULT_UTIL_CLASS));
    }

    /**
     * 读取标注了@DaoConfig的类
     */
    @SuppressWarnings("unchecked")
    private void readConfigClass() {
        for (Element configElement : configElements) {
            for (AnnotationMirror annotationMirror : configElement.getAnnotationMirrors()) {
                if (annotationMirror.getAnnotationType().toString().equals(ClassConstants.COPY_ANNOTATION_CLASS)) {
                    AnnotationValue copyAnnotation = annotationMirror.getElementValues().values()
                        .iterator().next();
                    List<String> annotationList = ((List<AnnotationValue>) copyAnnotation.getValue()).stream()
                        .map(annotationValue -> annotationValue.getValue().toString())
                        .toList();
                    copyAnnotations.addAll(annotationList);
                }
            }

            addTypeHandler(configElement);
            addResultHandler(configElement);
        }
    }

    /**
     * 添加类型处理器
     * @param configElement 配置类
     */
    private static void addTypeHandler(Element configElement) {
        ClassName className = ClassUtils.strToClass(configElement.asType().toString());
        for (Element enclosedElement : configElement.getEnclosedElements()) {
            if (enclosedElement.getKind() == ElementKind.METHOD && enclosedElement.getAnnotation(TypeHandler.class) != null) {
                ExecutableElement methodElement = (ExecutableElement) enclosedElement;
                String typeString = methodElement.getReturnType().toString();
                String methodName = methodElement.getSimpleName().toString();
                typeToHandlerMap.put(typeString, new Handler(className, methodName));
            }
        }
    }

    /**
     * 添加返回值处理器
     * @param configElement 配置类
     */
    private static void addResultHandler(Element configElement) {
        TypeName className = ClassName.get(configElement.asType());
        configElement.getEnclosedElements().stream()
            .filter(enclosedElement -> enclosedElement.getKind() == ElementKind.METHOD &&
                enclosedElement.getAnnotation(ResultHandler.class) != null)
            .map(enclosedElement -> (ExecutableElement) enclosedElement)
            .forEach(methodElement -> {
                String returnTypeClass = methodElement.getReturnType().toString();

                ReturnTypeInfo returnTypeInfo = new ReturnTypeInfo();
                if (returnTypeClass.contains(LEFT_SHARP_BRACE)) { // 有泛型
                    returnTypeInfo.returnClass = returnTypeClass.substring(0, returnTypeClass.indexOf(LEFT_SHARP_BRACE));
                    String[] genericTypes = returnTypeClass.substring(returnTypeClass.indexOf(LEFT_SHARP_BRACE) + 1, returnTypeClass.length() - 1).split(",");
                    for (String genericType : genericTypes) {
                        if (ORIGIN_TYPE.equals(genericType)) {
                            break;
                        }
                        returnTypeInfo.typePos++;
                    }
                } else {
                    returnTypeInfo.returnClass = returnTypeClass;
                }
                returnTypeInfo.className = className;
                returnTypeInfo.methodName = methodElement.getSimpleName().toString();
                returnTypeInfo.parameters = getParameterList(methodElement);

                returnTypeMap.put(returnTypeInfo.returnClass, returnTypeInfo);
            });
    }

    /**
     * 生成返回参数列表
     * @param methodElement 返回值处理器参数
     * @return 返回参数列表
     */
    private static String getParameterList(ExecutableElement methodElement) {
        StringJoiner joiner = new StringJoiner(",\n", LEFT_ROUND_BRACKET, RIGHT_ROUND_BRACKET);
        for (VariableElement parameter : methodElement.getParameters()) {
            String parameterContent;
            Mapper mapper = parameter.getAnnotation(Mapper.class);
            if (mapper != null) {
                parameterContent = "s -> jdbcTemplate.query%s(s, %s, parameters.toArray())"
                    .formatted(mapper.forOne() ? "ForObject" : "", mapper.value());
            } else {
                String parameterName = parameter.getSimpleName().toString();
                parameterContent = switch (parameterName) {
                    case ONE_FUNCTION -> "s -> jdbcTemplate.queryForObject(s, rowMapper, parameters.toArray())";
                    case MANY_FUNCTION -> "s -> jdbcTemplate.query(s, rowMapper, parameters.toArray())";
                    default -> parameterName;
                };
            }

            joiner.add(parameterContent);
        }
        return joiner.toString();
    }

    /**
     * 读取dao-config.properties配置文件
     */
    private void readConfigFile() {
        try {
            FileObject configFile = filer.getResource(StandardLocation.SOURCE_PATH, EMPTY, CONFIG_FILE_NAME);
            try (InputStream inputStream = configFile.openInputStream()) {
                Properties configProperties = new Properties();
                configProperties.load(inputStream);
                String engineLabel = configProperties.getProperty(ENGINE);
                if (engineLabel != null) {
                    GlobalConfig.engine = switch (engineLabel) {
                        case JDBC -> Engine.JDBC;
                        case HIBERNATE -> Engine.HIBERNATE;
                        default -> throw new RuntimeException("引擎标签不正确");
                    };
                }
                GlobalConfig.showTime = Boolean.parseBoolean(configProperties.getProperty(SHOW_TIME));
                GlobalConfig.mapperLocation = configProperties.getProperty(MAPPER_LOCATION);
                if (GlobalConfig.mapperLocation != null && !GlobalConfig.mapperLocation.isBlank()) {
                    String configFilePath = configFile.toUri().toString();
                    readMapper(configFilePath.substring(8, configFilePath.lastIndexOf('/')));
                }
            }
        } catch (IOException e) {
            System.out.println("No config file found.");
        }
    }

    private void readMapper(String resourcePath) {
        File mapperFolder = new File(resourcePath, GlobalConfig.mapperLocation);
        if (!mapperFolder.exists() || mapperFolder.isFile()) {
            throw new RuntimeException("mapperLocation设置异常");
        }
        traverseFolder(mapperFolder);
    }

    private void traverseFolder(File mapperFolder) {
        if (mapperFolder.isFile() && mapperFolder.getName().endsWith(".yml")) {
            try(var inputStream = new FileInputStream(mapperFolder)) {
                Iterable<Object> objects = new Yaml(new Constructor(MethodInfo.class, new LoaderOptions())).loadAll(inputStream);
                MethodInfo globalInfo = (MethodInfo) objects.iterator().next();
                if (globalInfo.namespace != null) {
                    List<MethodInfo> list = new ArrayList<>();
                    list.add(globalInfo);
                    for (Object object : objects) {
                        list.add(((MethodInfo) object));
                    }
                    methodInfoMap.put(globalInfo.namespace, list);
                }
            } catch (Exception ignored) {
            }
        } else if (mapperFolder.isDirectory()) {
            File[] files = mapperFolder.listFiles();
            if (files != null) {
                for (File file : files) {
                    traverseFolder(file);
                }
            }
        }
    }
}
